﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Xml.Linq;
using ProjectWizardManagementv1.Infrastructure.Static;
using Paragraph = System.Windows.Documents.Paragraph;
using Run = System.Windows.Documents.Run;
using TextAlignment = System.Windows.TextAlignment;

namespace ProjectWizardManagementv1.Infrastructure.Concrete
{
    public class WordDocxDocumentParser
    {
        private readonly XNamespace _w;
        private Dictionary<XName, Action<Style, XElement>> _runElements;
        private Dictionary<XName, Action<Paragraph, XElement>> _paragraphElements; 

        public WordDocxDocumentParser()
        {
            _w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
            CreateParagraphElementsDictionary();
            CreateRunElementsDictionary();
        }

        private void CreateParagraphElementsDictionary()
        {
            _paragraphElements = new Dictionary<XName, Action<Paragraph, XElement>>
                                {
                                    {_w + "jc", SetTextAlignment}
                                };
        }

        private void CreateRunElementsDictionary()
        {
            _runElements = new Dictionary<XName, Action<Style, XElement>>
                               {
                                   {_w + "color", SetColor}, 
                                   {_w + "rFonts", SetFont}, 
                                   {_w + "sz", SetSize},
                                   {_w + "b", SetBold},
                                   {_w + "strike", SetStrikeThrough},
                                   {_w + "i", SetItalic},
                                   {_w + "em", SetItalic},
                                   {_w + "u", SetUnderline}
                               };
        }

        public FlowDocument ParseDocument(XElement wordDoc)
        {
            var paragraphs = wordDoc.Descendants(_w + "p");
            var flowDocument = new FlowDocument();
            var documentList = new List();

            foreach (var paragraph in paragraphs)
            {
                var propertiesFromDoc = paragraph.Descendants(_w + "pPr").Descendants().ToList();

                if (IsListParagraph(propertiesFromDoc.Select(f => f).FirstOrDefault(f => f.Name == _w + "pStyle")))
                {
                    GenerateList(documentList, paragraph, propertiesFromDoc);
                }
                else
                {
                    if(documentList.ListItems.Count > 0)
                    {
                        flowDocument.Blocks.Add(documentList);
                        documentList = new List();
                    }
                        
                    flowDocument.Blocks.Add(GenerateParagraph(paragraph, propertiesFromDoc));
                }

               
            }

            return flowDocument;
        }

        private void GenerateList(List list, XElement paragraph, List<XElement> propertiesFromDoc)
        {
            var wpfParagraph = GenerateParagraph(paragraph, propertiesFromDoc);
            var numListProperties = propertiesFromDoc.FirstOrDefault(f => f.Name == _w + "numPr");

            if (numListProperties != null)
            {
                if(list.ListItems.Count == 0)
                    list.MarkerStyle = SetMarkerStyle(numListProperties);

                var level = int.Parse(numListProperties.Element(_w + "ilvl").FirstAttribute.Value);
                var currentList = list;

                for(int i = 0; i < level; i++)
                {
                    var listItem = currentList.ListItems.Last();
                    if(!listItem.Blocks.Any(b => b.GetType() == typeof(List)))
                        listItem.Blocks.Add(new List{MarkerStyle = SetMarkerStyle(numListProperties)});

                    currentList = listItem.Blocks.FirstOrDefault(b => b.GetType() == typeof(List)) as List;
                }

                currentList.ListItems.Add(new ListItem(wpfParagraph));
            }


        }

        private bool IsListParagraph(XElement paragraphType)
        {
            return paragraphType != null && paragraphType.FirstAttribute.Value == "ListParagraph";
        }

        private Paragraph GenerateParagraph(XElement paragraph, List<XElement> propertiesFromDoc)
        {
            var wpfParagraph = new Paragraph();
            foreach (var xElement in propertiesFromDoc)
            {
                if (_paragraphElements.ContainsKey(xElement.Name))
                    _paragraphElements[xElement.Name].Invoke(wpfParagraph, xElement);
            }

            var mainRunProperties = propertiesFromDoc.Descendants(_w + "rPr").Descendants().ToList();
            var mainRunStyle = CreateStyleFromProperties(mainRunProperties);

            var wordRuns = paragraph.Descendants(_w + "r").ToList();

            foreach (var wordRun in wordRuns)
            {
                var runProperties = wordRun.Descendants(_w + "rPr").Descendants().ToList();
                var runStyle = new Style { BasedOn = mainRunStyle };
                runStyle = CreateStyleFromProperties(runProperties, runStyle);

                var run = new Run(wordRun.Value) { Style = runStyle };
                wpfParagraph.Inlines.Add(run);
            }
            return wpfParagraph;
        }

        private Style CreateStyleFromProperties(IEnumerable<XElement> propertyElements)
        {
            return CreateStyleFromProperties(propertyElements, new Style());
        }

        private Style CreateStyleFromProperties(IEnumerable<XElement> propertyElements, Style style)
        {
            foreach (var element in propertyElements)
            {
                if (_runElements.ContainsKey(element.Name))
                    _runElements[element.Name].Invoke(style, element);
            }

            return style;
        }

        #region RunProperties
        private static void SetFont(Style style, XElement font)
        {
            if(font != null)
            {
                var converter = new FontFamilyConverter();
                style.Setters.Add(new Setter(TextElement.FontFamilyProperty, converter.ConvertFrom(font.FirstAttribute.Value)));
            }
        }

        private static void SetSize(Style style, XElement size)
        {
            var converter = new FontSizeConverter();
            style.Setters.Add(new Setter(TextElement.FontSizeProperty, converter.ConvertFrom(size.FirstAttribute.Value)));
        }

        private static void SetColor(Style style, XElement color)
        {
            if(color != null)
            {
                var converter = new BrushConverter();
                var colorObj = converter.ConvertFromString("#" + color.FirstAttribute.Value) as SolidColorBrush;
                style.Setters.Add(new Setter(TextElement.ForegroundProperty, colorObj));
            }
        }

        private void SetBold(Style style, XElement bold)
        {
            style.Setters.Add(new Setter(TextElement.FontWeightProperty, FontWeights.Bold));
        }

        private void SetItalic(Style style, XElement italic)
        {
            style.Setters.Add(new Setter(TextElement.FontStyleProperty, FontStyles.Italic));
        }

        private void SetStrikeThrough(Style style, XElement strikeThrough)
        {
            style.Setters.Add(new Setter(Inline.TextDecorationsProperty, TextDecorations.Strikethrough));
        }

        private void SetUnderline(Style style, XElement underline)
        {
            style.Setters.Add(new Setter(Inline.TextDecorationsProperty, TextDecorations.Underline));
        }
        #endregion

        #region ParagraphProperties
        private void SetTextAlignment(Paragraph paragraph, XElement element)
        {
            paragraph.TextAlignment = TextAlignment.Left;
            var value = element.FirstAttribute.Value.UpperFirstCharacter();
                if (Enum.GetNames(typeof(TextAlignment)).Contains(value))
                    paragraph.TextAlignment = (TextAlignment) Enum.Parse(typeof (TextAlignment), value);
        }

        private TextMarkerStyle SetMarkerStyle(XElement listing)
        {
            //Lists are complex in word document. Placeholder for now.
            var numIdValue = listing.Element(_w + "numId").FirstAttribute.Value;
            return TextMarkerStyle.Circle;  
        }
        #endregion
    }
}
                                    